#!/usr/bin/perl
use Test::More;

BEGIN {
    $basedir = $0;
    $basedir =~ s|(.*)/[^/]*|$1|;

    $test_count      = 0;
    $test_binder_ctx = 0;

    # Allow binder info to be shown.
    $v = $ARGV[0];
    if ($v) {
        if ( $v ne "-v" ) {
            plan skip_all => "Invalid option (use -v)";
        }
    }
    else {
        $v = " ";
    }

    # check if binder driver available and kernel/userspace versions.
    $result = system("/bin/sh $basedir/init_binder.sh $v 2>/dev/null");

    if ( $result >> 8 eq 0 ) {    # NO_BINDER_SUPPORT
        plan skip_all => "Binder not supported by kernel";
    }
    elsif ( $result >> 8 eq 1 ) {    # BASE_BINDER_SUPPORT
        $test_count += 7;
        $n = " ";                    # Use /dev/binder

        $kvercur = `uname -r`;
        chomp($kvercur);

        # From 5.0 security context can be returned
        $kverminstream = "5.0";
        $result        = `$basedir/../kvercmp $kvercur $kverminstream`;
        if ( $result >= 0 ) {
            $test_binder_ctx = 1;
            $test_count += 1;
        }
        else {
            # Warn about earlier kernels, may require patch
            # (backported to some earlier kernels).
            $kverminstream = "4.16";
            $result        = `$basedir/../kvercmp $kvercur $kverminstream`;
            if ( $result < 0 ) {
                print
"This $kvercur kernel may fail some tests, if so may require\n";
                print
"\"binder: Add thread->process_todo flag\" patch available from:\n";
                print "https://lore.kernel.org/patchwork/patch/851324/\n";
            }
        }
    }
    elsif ( $result >> 8 eq 2 ) {    # BINDERFS_SUPPORT
        $test_binder_ctx = 1;
        $test_count += 8;
        $n = "-n";                   # Use /dev/binder-test
    }
    elsif ( $result >> 8 eq 3 ) {    # BINDER_VER_ERROR
        plan skip_all => "Binder kernel/userspace versions differ";
    }
    else {                           # BINDER_ERROR
        plan skip_all => "Error checking Binder driver";
    }

    plan tests => $test_count;
}

sub service_start {
    my ( $service, $runcon_args, $args ) = @_;
    my $pid;
    my $flag = $service . "_flag";

    system("mkfifo $basedir/$flag");

    if ( ( $pid = fork() ) == 0 ) {
        exec "runcon $runcon_args $basedir/$service -f $basedir/$flag $args";
    }

    # Wait for it to initialize.
    system("read -t 5 <>$basedir/$flag");
    return $pid;
}

sub service_end {
    my ( $service, $pid ) = @_;
    my $flag = $service . "_flag";

    kill KILL, $pid;
    waitpid $pid, 0;
    system("rm -f $basedir/$flag");
}

$sm_pid = service_start( "manager", "-t test_binder_mgr_t", "$n $v" );
$sp_pid =
  service_start( "service_provider", "-t test_binder_provider_t", "$n $v" );

# 1 Verify that authorized client and service provider can communicate with the binder service manager.
$result = system "runcon -t test_binder_client_t $basedir/client $n $v -c -r 3";
ok( $result eq 0 );

# 2 Verify that client cannot call manager (no call perm).
$result =
  system
  "runcon -t test_binder_client_no_call_mgr_t $basedir/client $n $v -r 1 2>&1";
ok( $result >> 8 eq 125 );

# 3 Verify that client cannot call service provider (no call perm).
$result =
  system
  "runcon -t test_binder_client_no_call_sp_t $basedir/client $n $v -r 2 2>&1";
ok( $result >> 8 eq 141 );

# 4 Verify that client cannot communicate with service provider (no impersonate perm).
$result =
  system "runcon -t test_binder_client_no_im_t $basedir/client $n $v -r 2 2>&1";
ok( $result >> 8 eq 133 );

# 5 Verify that client cannot communicate with service provider (no transfer perm).
$result =
  system
  "runcon -t test_binder_client_no_transfer_t $basedir/client $n $v -r 2 2>&1";
ok( $result >> 8 eq 125 );

# Kill the service provider & manager before next tests:
service_end( "service_provider", $sp_pid );
service_end( "manager",          $sm_pid );

# 6 Verify that provider domain cannot become a manager (no set_context_mgr perm).
$result = system "runcon -t test_binder_provider_t $basedir/manager $n $v 2>&1";
ok( $result >> 8 eq 14 );

# 7 Test that selinux_binder_transfer_file() fails when fd { use } is denied by policy.
#   Note that this test requires the Reference Policy boolean "allow_domain_fd_use" set to FALSE.
#   (setsebool allow_domain_fd_use=0)
# 7a Start Manager
$sm_pid = service_start( "manager", "-t test_binder_mgr_t", "$n $v" );

# 7b Start Service Provider
$sp_pid = service_start( "service_provider", "-t test_binder_provider_no_fd_t",
    "$n $v" );

# 7c Verify that authorized client can communicate with the service provider, however the sp's binder fd passed
#    to the client will not be valid for service provider domain and binder will return BR_FAILED_REPLY.
$result =
  system "runcon -t test_binder_client_t $basedir/client $n $v -r2 2>&1";
ok( $result >> 8 eq 141 );

# Kill the service provider & manager
service_end( "service_provider", $sp_pid );
service_end( "manager",          $sm_pid );

if ($test_binder_ctx) {
    #### Binder return security context test ######################
    #
    $sm_pid = service_start( "manager", "-t test_binder_mgr_t", "$n $v" );
    $sp_pid = service_start(
        "service_provider",
        "-t test_binder_provider_t",
        "$n $v -e unconfined_u:unconfined_r:test_binder_client_t:s0-s0:c0.c1023"
    );

# 8 Verify that authorized client and service provider can communicate with the binder service manager.
#   Also check that the service provider can receive the Clients security context.
    $result =
      system "runcon -t test_binder_client_t $basedir/client $n $v -c -r 3";
    ok( $result eq 0 );

    # Kill the service provider & manager.
    service_end( "service_provider", $sp_pid );
    service_end( "manager",          $sm_pid );

    # Cleanup binderfs stuff.
    system("/bin/sh $basedir/cleanup_binder.sh $v 2>/dev/null");
}

exit;
